Conversation
`@doist/comms-sdk` has exported `isValidUuidV7Base58` since 0.11.1 (July), and it checks the v7 version nibble and variant bits on top of the base58 decode. The local `looksLikeOpaqueCommsId` checked neither, so a 21-char name like `EngineeringDiscussion` decoded to 16 bytes and read as an id. That collision is the only reason `resolveChannelRef` grew a name-first-then-getChannel fallback in #66, so both go: `getDirectChannelId` recognises an opaque id again, which also restores the workspace-agnostic behaviour a bare digit-free channel id had before #66. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
doistbot
left a comment
There was a problem hiding this comment.
Nice cleanup — swapping the local base58 check for the SDK's isValidUuidV7Base58 removes a duplicate we'd otherwise have to keep in sync with the SDK.
Few things worth tightening:
- Removing the id fallback from
resolveChannelRefbreaks bare digit-free channel ids for its direct callers (channel threads,channel members list/add/remove/set, andchannel update --workspace): a valid id likeCbjxNkWHJBwcaVkoTCRgMis now parsed as a name and fails withCHANNEL_NOT_FOUND. Consider restoring the fallback there viaisValidUuidV7Base58(parsed.name), or routing these commands throughgetDirectChannelIdfirst. - On the new direct-ID path, prefer matching a channel name in the current workspace before treating an unprefixed, digit-free token as an id. Since such names are valid, workspace-agnostic
getChannellookup meanstdc channel delete <name> --yescould target a channel with that name in another workspace if the user has permission there.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (4)
src/lib/refs.ts:355:
getDirectChannelIdnow already appliesgetOpaqueNameId, so the identical fallback inresolveChannelId(lines 327-330, including the "no name to protect" comment) is unreachable. Delete that fallback and comment so the opaque-name check has a single owner.src/lib/refs.test.ts:1041:
Cf9TR6CPC2dKQL5fB2EoLdoesn't exercise the SDK validator this block claims to cover. It contains digits, soparseRefreturns it astype: 'id'vialooksLikeRawId, andresolveConversationIdreturns it beforegetOpaqueNameId/isValidUuidV7Base58runs. That means this case would still pass if the SDK delegation were removed, so it adds no regression signal here. Drop it from this list (or move it to a digit-path test) so the "delegated to the SDK validator" block only contains digit-free ids that actually reachisValidUuidV7Base58.src/lib/refs.test.ts:1040: This block largely restates existing coverage:
resolveConversationId('CDMDzXhBNCgyQZjkDnqwG')is already asserted in theresolveConversationIddescribe (line 663), andnopeonly exercises the trivial length rejection. The genuinely new signal is the valid-id-vs-16-byte-name distinction — trimming the block to one accepted id plus 'EngineeringDiscussion'/'CustomerSuccessLeadership' would keep that without the duplication.src/lib/refs.test.ts:1037: The comment says "the look-alikes below decode to 16 bytes", but 'CustomerSuccessLeadership' is 25 characters (~19 bytes decoded) and 'nope' doesn't decode to 16 bytes either. Only 'EngineeringDiscussion' matches the claim, so the comment overstates what the data exercises — worth rewording so a future reader doesn't infer the validator keys on a 16-byte decode.
The previous commit moved bare-id recognition into getDirectChannelId and deleted the fallback from resolveChannelRef. Two regressions against 3.4.1: - channel threads, channel members and channel update --workspace call resolveChannelRef directly, so a bare digit-free channel id stopped resolving there at all. - channel archive, delete and update went id-first, through a workspace-agnostic getChannel, where 3.4.1 tried a name in the current workspace first. Back to 3.4.1's order in both places, with the SDK's validator in place of the local check. search and thread create keep resolving bare ids, since resolveChannelId has its own check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found by mutation: letting the fallback fire on any name failure, not just CHANNEL_NOT_FOUND, passed every test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeping as-is. True of 7f5ff7b, but
Fixed all three in 551ec77. The block is down to the one contrast it's for: |
Deleting the opaque-id fallback from resolveCommentId or resolveMessageId left the suite green; the other four call sites were already pinned. Also corrects two comments that still described the old decode-only check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@doistbot /review |
doistbot
left a comment
There was a problem hiding this comment.
This PR swaps the local base58 id check for the SDK's isValidUuidV7Base58, removing the duplicate validator added in #66. The refactor is clean: getOpaqueNameId delegates to the SDK validator, the name-then-id fallback in resolveChannelRef keeps workspace membership enforcement intact, and the tests cover the accepted-id vs. 16-byte-name distinction well. No inline issues were flagged.
I also included a few optional follow-up notes in the details below.
Optional follow-up notes (2)
src/lib/refs.test.ts:377:
getDirectChannelIdreturnsnullfor any bare digit-free name without calling the SDK validator—even for the valid ID in the next test. This assertion cannot verify that the validator rejectsEngineeringDiscussion. Remove the validator rationale here; theresolveConversationIdtest below covers that distinction.src/lib/refs.test.ts:645:
refuses an id that belongs to another workspacenever asserts the id fallback actually ran. With an empty channel list,resolveChannelRef(id, 1)already rejects withCHANNEL_NOT_FOUNDfrommatchByName, so this test passes unchanged if the fallback is removed orgetOpaqueNameIdstops recognizingCbjxNkWHJBwcaVkoTCRgM. The named behavior (fetching a cross-workspace channel viagetChanneland refusing it) isn't pinned. Addexpect(mockGetChannel).toHaveBeenCalledWith(id), matching the siblingit.eachand fallback tests, so the workspace check is actually exercised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixed in 5349b9b. The comment now says every bare token goes to the name path there, and points at the
Fixed in 5349b9b. Added |
Overview
The id check added in #66 was a duplicate of the SDK's
isValidUuidV7Base58, which I only spotted after it merged. This removes ours in favour of the SDK one.Reference
Follows @scottlovegrove's review on #66. The error helpers from that PR (
isNotFound,isConflict,isMalformedId) move to the SDK in Doist/comms-sdk-typescript#83, and this repo will pick them up once that's released.Changelog
A token that looks like an id but isn't one, such as
EngineeringDiscussion, is now refused locally withINVALID_REFrather than sent to Comms and refused there. Real ids behave as before.Test plan
tdc channel threads <a channel id with no digit in it> --limit 1tdc channel archive <the same id> --dry-run[dry-run] Would archive channelnaming that channeltdc search x --channel <the same id> --limit 1INVALID_REFtdc channel threads EngineeringDiscussion --limit 1Error: CHANNEL_NOT_FOUND: it decodes to 16 bytes but isn't a UUIDv7, so it stays a nametdc conversation done <a conversation id with no digit in it> --dry-run[dry-run] Would archive conversation, the case Bare opaque ids without a digit are rejected, and bad ids surface as raw SDK errors #65 was aboutAdversarial review
551ec77Doistbot: rounds and P2s filled in at merge.
🤖 Generated with Claude Code